﻿using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Input;
using CLEX.Service.DataContracts;
using Windows.Foundation;
using Windows.UI;
using Windows.UI.Xaml.Media;

namespace CLEX.Client.Metro.ViewModel.Lattice
{
    public class LatticeNode : VMBase
    {
        private readonly LatticeState state;
        private readonly Point position;
        private readonly ReadOnlyCollection<string> groupByColumns;
        private readonly RelayCommand select;

        public LatticeNode(LatticeState state, Point position, IEnumerable<string> groupByColumns)
        {
            this.state = state;
            this.position = position;
            this.groupByColumns = new ReadOnlyCollection<string>(groupByColumns.ToList());
            this.select = new RelayCommand(DoSelect);

            state.GroupByColumnsChanged += State_GroupByColumnsChanged;
        }

        private void State_GroupByColumnsChanged()
        {
            RaisePropertyChanged("Background");
        }

        public Point Position
        {
            get { return position; }
        }

        public IEnumerable<string> GroupByColumns
        {
            get { return groupByColumns; }
        }

        public string DisplayName
        {
            get
            {
                StringBuilder result = new StringBuilder();
                for (int i = 0; i < groupByColumns.Count; i++)
                {
                    result.Append(groupByColumns[i]);
                    if (i + 1 < groupByColumns.Count)
                    {
                        result.Append(",");
                    }
                }
                if (result.Length == 0)
                {
                    return "*";
                }
                return result.ToString();
            }
        }

        public ICommand Select
        {
            get { return select; }
        }

        private void DoSelect()
        {
            state.GroupByColumns = groupByColumns;
        }

        public Brush Background
        {
            get { return new HashSet<string>(state.GroupByColumns).SetEquals(this.groupByColumns) ? new SolidColorBrush(Colors.Red) : new SolidColorBrush(Colors.Transparent); }
        }
    }

    public class LatticeBuilder
    {
        private readonly ReadOnlyCollection<LatticeNode> nodes;

        public LatticeBuilder(LatticeState state, IEnumerable<string> columns)
        {
            Dictionary<Point, List<string>> groups = new Dictionary<Point, List<string>>();
            var set = columns.GetPowerSet().ToLookup(p => p.Count);
            double maxX = double.MinValue;
            double maxY = double.MinValue;
            double minX = double.MaxValue;
            double minY = double.MaxValue;
            foreach (var v in set)
            {
                double y = v.Key;
                int itemCount = 0;
                foreach (var item in v)
                {
                    double x = itemCount - (double)v.Count() / 2.0;
                    groups.Add(new Point(x, y), item);
                    maxX = Math.Max(maxX, x);
                    maxY = Math.Max(maxY, y);
                    minX = Math.Min(minX, x);
                    minY = Math.Min(minY, y);
                    itemCount++;
                }
            }

            double s = 500.0;
            double xr = maxX - minX;
            double yr = maxY - minY;
            this.nodes = new ReadOnlyCollection<LatticeNode>(groups.Select(p => new LatticeNode(state, new Point((p.Key.X - minX) / xr * s, (p.Key.Y - minY) / yr * s), p.Value)).ToList());
        }

        public IEnumerable<LatticeNode> Nodes
        {
            get { return nodes; }
        }
    }

    public class BarVM : VMBase
    {
        private readonly string label;
        private readonly double height;

        public BarVM(string label, double height)
        {
            this.label = label;
            this.height = height;
        }

        public double Height
        {
            get { return height; }
        }

        public string Label
        {
            get { return label; }
        }
    }

    public class HistogramVM : VMBase
    {
        private readonly AggregateData aggregate;
        private readonly ObservableCollection<BarVM> bars = new ObservableCollection<BarVM>();

        public AggregateData Aggregate
        {
            get { return aggregate; }
        }

        public ObservableCollection<BarVM> Bars
        {
            get { return bars; }
        } 

        public string DisplayName
        {
            get { return string.Format("{0}({1})", aggregate.AggregateType.ToString(), aggregate.Column); }
        }

        public HistogramVM(AggregateData aggregate)
        {
            this.aggregate = aggregate;
        }
    }

    public class LatticeVM : VMBase
    {
        private readonly IFacade facade;
        private readonly UserData user;
        private readonly DatabaseData database;
        private readonly TableData table;
        private readonly LatticeState state;
        private readonly RelayCommand editLattice;
        private readonly RelayCommand deleteHistogram;
        private readonly RelayCommand newHistogram;
        private readonly RelayCommand back;
        private readonly RelayCommand refresh;
        private readonly ObservableCollection<LatticeNode> nodes = new ObservableCollection<LatticeNode>();
        private readonly ObservableCollection<HistogramVM> histograms = new ObservableCollection<HistogramVM>();
        private HistogramVM selectedHistogram;

        public ObservableCollection<LatticeNode> Nodes
        {
            get { return nodes; }
        }

        public ObservableCollection<HistogramVM> Histograms
        {
            get { return histograms; }
        }

        public ICommand EditLattice
        {
            get { return editLattice; }
        }

        public ICommand DeleteHistogram
        {
            get { return deleteHistogram; }
        }

        public ICommand NewHistogram
        {
            get { return newHistogram; }
        }

        public ICommand Back
        {
            get { return back; }
        }

        public RelayCommand Refresh
        {
            get { return refresh; }
        } 

        public HistogramVM SelectedHistogram
        {
            get { return selectedHistogram; }
            set
            {
                if (selectedHistogram != value)
                {
                    selectedHistogram = value;
                    RaisePropertyChanged("SelectedHistogram");
                    deleteHistogram.RaiseCanExecuteChanged();
                }
            }
        }

        public LatticeVM(IFacade facade, UserData user, DatabaseData database, TableData table, LatticeState state)
        {
            this.facade = facade;
            this.user = user;
            this.database = database;
            this.table = table;
            this.state = state;
            this.editLattice = new RelayCommand(DoEditLattice);
            this.deleteHistogram = new RelayCommand(DoDeleteHistogram, CanDeleteHistogram);
            this.newHistogram = new RelayCommand(DoNewHistogram);
            this.back = new RelayCommand(DoBack);
            this.refresh = new RelayCommand(DoRefresh);

            this.state.LatticeColumnsChanged += State_LatticeColumnsChanged;
            if (state.LatticeColumns.Count() == 0)
            {
                facade.ShowLatticeEdit(table, state);
            }

            this.state.AggregateAdded += State_AggregateAdded;
            this.state.AggregateRemoved += State_AggregateRemoved;
        }

        private void State_AggregateRemoved(AggregateData aggregate)
        {
            for (int i = 0; i < histograms.Count; i++)
            {
                if (histograms[i].Aggregate == aggregate)
                {
                    histograms.RemoveAt(i);
                    break;
                }
            }
        }

        private void State_AggregateAdded(AggregateData aggregate)
        {
            histograms.Add(new HistogramVM(aggregate));
        }

        private void State_LatticeColumnsChanged()
        {
            nodes.Clear();
            LatticeBuilder builder = new LatticeBuilder(state, state.LatticeColumns);
            nodes.AddRange(builder.Nodes);
        }

        private void DoEditLattice()
        {
            try
            {
                facade.ShowLatticeEdit(table, state);
            }
            catch (Exception ex)
            {
                facade.ShowUnexpectedError("Unable to edit the lattice.", ex.Message);
            }
        }

        private void DoDeleteHistogram()
        {
            facade.ShowHistogramDelete(state, selectedHistogram.Aggregate);
        }

        private bool CanDeleteHistogram()
        {
            return selectedHistogram != null;
        }

        private void DoNewHistogram()
        {
            facade.ShowHistogramNew(table, state);
        }

        private void DoBack()
        {
            facade.GoToHome(user);
        }

        private void DoRefresh()
        {
            QueryData data = new QueryData(
                tableName: table.Catalog + "." + table.Schema + "." + table.Name,
                maxResultCount: int.MaxValue,
                groupByColumns: state.GroupByColumns,
                aggregates: state.Aggregates);

            List<HistogramRowData> histograms;
            
            try
            {
                histograms = facade.ComputeHistograms(database.ConnectionString, data);
            }
            catch (Exception ex)
            {
                facade.ShowUnexpectedError("Unable to compute the histograms", ex.Message);
                return;
            }

            foreach (var hist in this.histograms)
            {
                hist.Bars.Clear();
            }
            foreach (HistogramRowData row in histograms)
            {
                for (int i = 0; i < this.histograms.Count; i++)
                {
                    this.histograms[i].Bars.Add(new BarVM(row.Group, row.Values[i]));
                }
            }
        }
    }
}
